package erfgame.core.world.terrain;

import java.awt.geom.Point2D;
import java.util.logging.Logger;

import erfgame.core.Point3D;

public final class RotatedVoxelColorPicker extends AbstractVoxelColorPicker {
	
	private static final Logger log = Logger.getLogger( RotatedVoxelColorPicker.class.getName() );

	private int[][] verticalAtanValues;
	private int[][] horizontalAtanValues;
	
	private double verticalLightAngle;
	private double horizontalLightAngle;
	
	private TerrainColorSource unknownColorSource;
	
	private Point3D xzresult;
	private Point3D yzresult;
	private Point3D xyresult;
	
	public RotatedVoxelColorPicker( double horizontalLightAngle, double verticalLightAngle, int smoothness, TerrainColorSource unknownColorSource, int blockWidth, int blockHeight, int blockDepth, boolean useLog ) {
		
		super( smoothness, blockWidth, blockHeight, blockDepth, useLog );
		this.verticalAtanValues = generateAtanValues( verticalLightAngle, smoothness, false );
		this.horizontalAtanValues = generateAtanValues( horizontalLightAngle, smoothness, false );
		this.unknownColorSource = unknownColorSource;
		this.verticalLightAngle = verticalLightAngle;
		this.horizontalLightAngle = horizontalLightAngle;
		
		this.xzresult = new Point3D();
		this.yzresult = new Point3D();
		this.xyresult = new Point3D();
	}
	
	public final int atan2sinVertical( int a, int b ) {
		return verticalAtanValues[b + smoothnessTimes2][a + smoothnessTimes2];
	}
	
	public final int atan2sinHorizontal( int a, int b ) {
		return horizontalAtanValues[b + smoothnessTimes2][a + smoothnessTimes2];
	}
	
	public final int getColor( 
			TerrainColorSource colorSource,
			int cx, 
			int cy, 
			int cz,
			byte[][][][][][] terrain, 
			int tx, 
			int ty, 
			int tz, 
			int dCeiling, 
			int worldX, 
			int worldY, 
			int worldZ,
			boolean clearRoof
	) {
		Point3D xzresult = this.xzresult;
		Point3D yzresult = this.yzresult;
		Point3D xyresult = this.xyresult;
		
		// TODO use internal values
		VerticalEdge xzturn = new VerticalEdge();
		VerticalEdge yzturn = new VerticalEdge();
		
		boolean xzValid = getPathDeltas(
				cx, 
				cy, 
				cz, 
				terrain, 
				tx, 
				ty, 
				tz, 
				XZ_LEFT_HANDED, 
				XZ_RIGHT_HANDED, 
				false,
				xzresult,
				xzturn,
				clearRoof
		);

		boolean yzValid = getPathDeltas(
				cx, 
				cy, 
				cz, 
				terrain, 
				tx, 
				ty, 
				tz, 
				YZ_LEFT_HANDED, 
				YZ_RIGHT_HANDED, 
				false,
				yzresult,
				yzturn,
				clearRoof
		);
		
		int mult;

		int xycx;
		int xycy;
		int xycz;
		int xytx;
		int xyty;
		int xytz;
		int xyremainingLength;
		if( (xzturn.remainingLength != 0 || yzturn.remainingLength != 0) ) {
			if( xzturn.remainingLength > yzturn.remainingLength ) {
				xytx = xzturn.x;
				xyty = xzturn.y;
				xytz = xzturn.z;
				xycx = xzturn.cx;
				xycy = xzturn.cy;
				xycz = xzturn.cz;
				xyremainingLength = xzturn.remainingLength;
			} else {
				xytx = yzturn.x;
				xyty = yzturn.y;
				xytz = yzturn.z;
				xycx = yzturn.cx;
				xycy = yzturn.cy;
				xycz = yzturn.cz;
				xyremainingLength = yzturn.remainingLength;
			}
		} else {
			xytx = tx;
			xyty = ty;
			xytz = tz;
			xycx = cx;
			xycy = cy;
			xycz = cz;
			xyremainingLength = smoothnessMinus1;
		}
		
		boolean xyValid;
		
		if( smoothnessMinus1 - xyremainingLength < 4 ) {
			xyValid = getPathDeltas(
					xycx, 
					xycy, 
					xycz, 
					terrain, 
					xytx, 
					xyty, 
					xytz, 
					XY_LEFT_HANDED, 
					XY_RIGHT_HANDED, 
					false,
					xyresult,
					null,
					clearRoof
			);	
		} else {
			// too far away
			xyValid = false;
		}
		
		Point3D.Double xyrotated = new Point3D.Double();
		if( xyValid ) {
			rotate( xyresult, xyrotated );
		}
		Point3D.Double yzrotated = new Point3D.Double();
		if( yzValid ) {
			rotate( yzresult, yzrotated );
		}
		Point3D.Double xzrotated = new Point3D.Double();
		if( xzValid ) {
			rotate( xzresult, xzrotated );
		}
		
//    	if( xyValid && xzValid && yzValid ) {
//			// pick best two 
//			if( yzrotated.x == xzrotated.x ) {
//				// the slope is parallel to the light angle, drop one
//				if( yzrotated.y != 0 ) {
//					xzValid = false;
//				} else {
//					yzValid = false;					
//				}
//			} else {
//				double xyQuality = Math.abs(xyrotated.x * xyrotated.y * xyrotated.z);
//				double xzQuality = Math.abs(xzrotated.x * xzrotated.y * xzrotated.z);
//				double yzQuality = Math.abs(yzrotated.x * yzrotated.y * yzrotated.z);
//				if( xyQuality > xzQuality && yzQuality > xzQuality ) {
//	                xzValid = false;
//				} else if( xyQuality > yzQuality ) {
//					yzValid = false;
//				} else {
//					xyValid = false;
//				}				
//			}
//		}

		
		if( xzValid && yzValid ) {
			double m = (yzrotated.y - xzrotated.y)/(yzrotated.x - xzrotated.x);
			// intersection with the x axis
			double xvi = yzrotated.x - yzrotated.y/m;
			double zvi = ((yzrotated.z - xzrotated.z)*(xvi-xzrotated.x))/(yzrotated.x - xzrotated.x) + xzrotated.z;
			double verticalAngle = Math.atan2(zvi, xvi);
			double verticalSin = Math.sin( verticalAngle );

			// intersection with the y axis 
			double horizontalAngle;
			double yhi = yzrotated.y - m * yzrotated.x; 
			double zhi = ((yzrotated.z - xzrotated.z)*(yhi-xzrotated.y))/(yzrotated.y - xzrotated.y) + xzrotated.z;
			horizontalAngle = Math.atan2(zhi, yhi) + Math.PI/2;
			double horizontalSin = Math.sin( horizontalAngle );

//			mult = (int)(ONE * horizontalSin * Math.abs( verticalSin ) );
			mult = (int)(ONE * verticalSin );			
			
		} else if( xyValid && xzValid ) { 
			double verticalAngle = Math.atan2(xzrotated.z, xzrotated.x);
			double verticalSin = Math.sin( verticalAngle );			

			double horizontalAngle = Math.atan2( xyrotated.y, xyrotated.x );
			double horizontalSin = Math.sin( horizontalAngle );

//			mult = (int)(ONE * horizontalSin * Math.abs( verticalSin ) );
			mult = (int)(ONE * horizontalSin );			
		} else if( xyValid && yzValid ) {
			double verticalAngle = Math.atan2(yzrotated.z, yzrotated.x);
			double verticalSin = Math.sin( verticalAngle );			

			double horizontalAngle = Math.atan2( xyrotated.y, xyrotated.x );
			double horizontalSin = Math.sin( horizontalAngle );

//			mult = (int)(ONE * horizontalSin * Math.abs( verticalSin ) );
			mult = (int)(ONE * horizontalSin );			
		} else {
			return this.unknownColorSource.getColor(worldX, worldY, worldZ, 0, 0, dCeiling);			
		}
			
		int color = colorSource.getColor( 
				worldX, 
				worldY, 
				worldZ, 
				mult, 
				PRECISION, 
				dCeiling 
		);
		
//		if( !diagValid ) {
//			color = 0xFFFF0000;
//		}
		
		return color;
	}	
	
	private void rotate( Point3D p, Point3D.Double result ) {
		double hsin = Math.sin( -horizontalLightAngle );
		double hcos = Math.cos( -horizontalLightAngle );
		// rotate around z
		result.x = p.x * hcos - p.y * hsin;
		result.y = p.x * hsin + p.y * hcos;
		double vsin = Math.sin( -verticalLightAngle );
		double vcos = Math.cos( -verticalLightAngle );
		// rotate around y
		result.z = result.x * vsin + p.z * hcos;
		result.x = result.x * vcos - p.z * vsin;
	}
	
}
